/*
* Copyright (C) 2000 by the GRASS Development Team
* Author: Bob Covill <bcovill@tekmap.ns.ca>
* 
* This Program is free software under the GPL (>=v2)
* Read the file COPYING coming with GRASS for details
*
*
*/

/* d.nviv -- interactively create fly-through
 * script for NVIZ
 * Functions ********************************
 * main --	parse parameters and get key frame coorinates
 * do_profile --calculate camera and eye coordinates from
 *     		raster map
 * move -- 	part of screen coords
 * cont --	part of screen coords 
 * read_rast -- return camera and eye coordinates
***********************************/

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <grass/gis.h>
#include <grass/display.h>
#include <grass/raster.h>
#include "local.h"
#include <grass/glocale.h>

int cut_val, frame = 0;
int height_flag = 0;
double OLD_DEPTH;
char img_name[512];
int no_frames;
int cnt = 1;
int key_frames = 0;
int off_screen = 0;
double key_time = 0.;
FILE *fp, *fp2;

int main(int argc, char *argv[])
{
    char *name, *mapset;
    char outfile[GNAME_MAX];
    int fd, projection;
    char buf[512], buf1[1024], buf2[1024];
    int screen_x, screen_y, button;
    int i, k;
    int frame_start = 0;
    double e1, e2, n1, n2;
    RASTER_MAP_TYPE data_type;
    struct Cell_head window;
    struct
    {
	struct Option *opt1, *route, *name, *output, *dist, *ht, *frames,
	    *start;
	struct Flag *i, *f, *c, *k, *o, *e;
    }
    parm;
    struct GModule *module;

    G_gisinit(argv[0]);

    /* Set description */
    module = G_define_module();
    module->keywords = _("display");
    module->description = _("Creates fly-through script to run in NVIZ.");

    parm.opt1 = G_define_standard_option(G_OPT_R_INPUT);

    parm.output = G_define_standard_option(G_OPT_F_OUTPUT);
    parm.output->description = _("Name of output script");

    parm.name = G_define_option();
    parm.name->key = "name";
    parm.name->type = TYPE_STRING;
    parm.name->required = NO;
    parm.name->description = _("Prefix of output images (default = NVIZ)");

    parm.route= G_define_option();
    parm.route->key = "route";
    parm.route->type = TYPE_STRING;
    parm.route->required = NO;
    parm.route->multiple = YES;
    parm.route->key_desc = "east,north";
    parm.route->description = _("Route coordinates (east,north)");

    parm.dist = G_define_option();
    parm.dist->key = "dist";
    parm.dist->type = TYPE_DOUBLE;
    parm.dist->required = YES;
    parm.dist->description = _("Camera layback distance (in map units)");

    parm.ht = G_define_option();
    parm.ht->key = "ht";
    parm.ht->type = TYPE_DOUBLE;
    parm.ht->required = YES;
    parm.ht->description = _("Camera height above terrain");

    parm.frames = G_define_option();
    parm.frames->key = "frames";
    parm.frames->type = TYPE_INTEGER;
    parm.frames->required = YES;
    parm.frames->description = _("Number of frames");

    parm.start = G_define_option();
    parm.start->key = "start";
    parm.start->type = TYPE_INTEGER;
    parm.start->required = NO;
    parm.start->description = _("Start frame number (default=0)");


    parm.i = G_define_flag();
    parm.i->key = 'i';
    parm.i->description = _("Interactively select route");

    parm.f = G_define_flag();
    parm.f->key = 'f';
    parm.f->description = _("Full render -- Save images");

    parm.c = G_define_flag();
    parm.c->key = 'c';
    parm.c->description = _("Fly at constant elevation (ht)");

    parm.k = G_define_flag();
    parm.k->key = 'k';
    parm.k->description = _("Include command in the script to output a KeyFrame file");

    parm.o = G_define_flag();
    parm.o->key = 'o';
    parm.o->description = _("Render images off-screen");

    parm.e = G_define_flag();
    parm.e->key = 'e';
    parm.e->description = _("Enable vector and sites drawing");


    if (G_parser(argc, argv))
	exit(EXIT_FAILURE);

/* check arguments */
    if ((!parm.i->answer) && (!parm.route->answer))
	G_fatal_error( _("Either -i flag and/or route parameter must be used"));

/* get GRASS parameters */
    G_get_window(&window);
    projection = G_projection();
    D_do_conversions(&window, 0, 1, 0, 1);

/* setup screen coords */
    screen_x = ((int) D_get_d_west() + (int) D_get_d_east()) / 2;
    screen_y = ((int) D_get_d_north() + (int) D_get_d_south()) / 2;

/* get camera parameters */
    DIST = atof(parm.dist->answer);
    HT = atof(parm.ht->answer);
    no_frames = atoi(parm.frames->answer);

    if (parm.start->answer)
	frame_start = atoi(parm.start->answer);

    if (parm.c->answer)
	height_flag = 1;
    if (parm.k->answer)
	key_frames = 1;
    if (parm.o->answer && !parm.f->answer)
	G_fatal_error( _("Off-screen only available with full render mode"));
    if (parm.o->answer)
	off_screen = 1;

/* Initialize coords */
    e1 = e2 = n1 = n2 = -9999.;

    G_begin_distance_calculations();

/* Open Input File for reading */
    name = parm.opt1->answer;

/* Open Raster File*/
    if (NULL == (mapset = G_find_cell2(name, "")))
	G_fatal_error( _("Raster map <%s> not found"), name);
    if (0 > (fd = G_open_cell_old(name, mapset)))
	G_fatal_error( _("Unable to open raster map <%s>"), name);

/* Set Image name */
    if (parm.name->answer)
	sprintf(img_name, parm.name->answer);
    else
	sprintf(img_name, "NVIZ");


/* Open ASCII file for output */
    /* append ".nvscr" to filename if it doesn't already have it */
    strncpy(outfile, parm.output->answer, GNAME_MAX-7);
    outfile[GNAME_MAX-7] = '\0'; /* strncpy() doesn't null terminate the string */
    if(strcmp(&outfile[strlen(outfile) - 6], ".nvscr") != 0 )
	strcat(outfile, ".nvscr");

    if (NULL == (fp = fopen(outfile, "w")))
	G_fatal_error( _("Unable to open file <%s>"), outfile);

/* Get Raster Type */
    data_type = G_get_raster_map_type(fd);
/* Done with file */

/* Output initial startup stuff */
    sprintf(buf1,
	    "## REGION: n=%f s=%f e=%f w=%f\n## Input=%s Dist=%f Ht=%f\n",
	    window.north, window.south, window.east, window.west, outfile,
	    DIST, HT);

    sprintf(buf2, "\nset FRAMES %d\n", no_frames);
    strcat(buf1, buf2);
    fprintf(fp, "%s", buf1);

    sprintf(buf1, "SendScriptLine \"Nclear_keys\"");
    sprintf(buf2, "\nSendScriptLine \"Nupdate_frames\"");
    strcat(buf1, buf2);
    fprintf(fp, "%s", buf1);

    sprintf(buf1, "\nSendScriptLine \"Nset_numsteps $FRAMES\"");
    sprintf(buf2, "\nSendScriptLine \"Nupdate_frames\"\n");
    strcat(buf1, buf2);
    fprintf(fp, "%s", buf1);

/* Use Linear mode for smooth frame transition */
    sprintf(buf1, "\nSendScriptLine \"Nset_interp_mode linear\"");
    sprintf(buf2, "\nSendScriptLine \"Nupdate_frames\"\n\n");
    strcat(buf1, buf2);
    fprintf(fp, "%s", buf1);

/* eanble vector and sites drawing */
    if (parm.e->answer) {
    sprintf(buf1, "\nSendScriptLine \"Nshow_vect on\"");
    sprintf(buf2, "\nSendScriptLine \"Nshow_sites on\"\n\n");
    strcat(buf1, buf2);
    fprintf(fp, "%s", buf1);
    }

/* Get coords */
/* Select points interactively*/
    if (parm.i->answer) {
	int count = 0;
	if (R_open_driver() != 0)
		G_fatal_error ( _("No graphics device selected"));
	D_setup(0);

	G_setup_plot(D_get_d_north(),
		     D_get_d_south(),
		     D_get_d_west(), D_get_d_east(), move, cont);

	dist = 0;

	fprintf(stderr, "\n\n");
	fprintf(stderr, "Use mouse to select Start Point\n");
	R_get_location_with_pointer(&screen_x, &screen_y, &button);
	e1 = D_d_to_u_col((double) screen_x);
	n1 = D_d_to_u_row((double) screen_y);

	fprintf(stderr, "\nUse mouse to select route \n");
	fprintf(stderr, "Buttons:\n");
	fprintf(stderr, "Left:   Mark next point\n");
	fprintf(stderr, "Middle: Mark next point\n");
	fprintf(stderr, "Right:  Finish route and exit\n\n");

	while (button != 3) {
	    count++;
	    R_get_location_with_line( (int)(0.5+ D_u_to_d_col(e1)),
		(int)(0.5+ D_u_to_d_row(n1)), &screen_x, &screen_y, &button);
	    if (button == 1 || button == 2) {
		e2 = D_d_to_u_col((double) screen_x);
		n2 = D_d_to_u_row((double) screen_y);
	    }
	    else {
		if (e2 == -9999. || n2 == -9999.) {
		    G_fatal_error( _("You must select more than one point"));
		    
		}
		break;
	    }

/* draw line from p1 to p2 */
	    G_plot_line(e1, n1, e2, n2);

/* Get profile info */
	    do_profile(e1, e2, n1, n2, name, fd, data_type);

	    n1 = n2;
	    e1 = e2;
	    R_stabilize();
	}

	if (count < 4) {
	    G_fatal_error( _("You must select at least four points"));

	}

	R_close_driver();

    }
    else {

/* Coords from Command Line */
	for (i = 0; parm.route->answers[i]; i += 2) {
	    /* Test for number coordinate pairs */
	    k = i;
	}

	if (k < 6) {
/* Only one coordinate pair supplied */
	    G_fatal_error( _("You must provide at least four points %d"), k);

	}
	else {
	    for (i = 0; i <= k - 2; i += 2) {
		sscanf(parm.route->answers[i], "%lf", &e1);
		sscanf(parm.route->answers[i + 1], "%lf", &n1);
		sscanf(parm.route->answers[i + 2], "%lf", &e2);
		sscanf(parm.route->answers[i + 3], "%lf", &n2);
/* Get profile info */
		do_profile(e1, e2, n1, n2, name, fd, data_type);
		
/* Get last coord */
		if (i == k - 2)
		    do_profile(e2, e2, n2, n2, name, fd, data_type);
	    }
	}
    }				/* done with coordinates */


/* Output final part of script */
/* generate key-frame script */
    if (key_frames) {
	strcpy(buf, outfile);
	buf[strlen(outfile)-6] = '\0'; /* skip extension */
	strcat(buf, ".kanim");
	fprintf(fp, "\n## The following saves the animation to a format\n");
	fprintf(fp, "## suitable for editting with the kanimator panel\n");
	fprintf(fp, "SendScriptLine \"Nprint_keys %s\"\n", buf);
	fprintf(fp, "puts \"Saving Key Frame file %s\"\n", buf);
    }

/* output off-screen option */
    if (off_screen) {
	fprintf(fp, "\n## Off screen rendering enabled \n");
	fprintf(fp, "## Ensure main window is minimized before running\n");
	fprintf(fp, "SendScriptLine \"Noff_screen 1\"\n");
    }

    fprintf(fp, "\n\nset num %d", frame_start);
    fprintf(fp, "\n\nfor {set frame 1} {$frame <= $FRAMES} {incr frame} {");

    if (parm.f->answer) {
/* Full render and save */
	fprintf(fp, "\nset name %s", img_name);
	fprintf(fp, "\nset num2 [format \"\%%04d\" $num]");
	fprintf(fp, "\nappend name $num2 \".ppm\"");
	fprintf(fp, "\nSendScriptLine \"Ndo_framestep $frame 1\"");
	fprintf(fp, "\nSendScriptLine \"Nwrite_ppm $name \"");
	fprintf(fp, "\nincr num");
    }
    else {
/* Quick draw wire */
/* output full variables commented so can be easily changed */
	fprintf(fp, "\nset name %s", img_name);
	fprintf(fp, "\nset num2 [format \"\%%04d\" $num]");
	fprintf(fp, "\nappend name $num2 \".ppm\"");
	fprintf(fp,
		"\n## To render in full set to 1 and uncomment Nwrite_ppm \"");
	fprintf(fp, "\nSendScriptLine \"Ndo_framestep $frame 0\"");
	fprintf(fp, "\n#SendScriptLine \"Nwrite_ppm $name \"");
	fprintf(fp, "\nincr num");
    }

    fprintf(fp, "\n}\n");
    if (off_screen)
	fprintf(fp, "SendScriptLine \"Noff_screen 0\"\n");

    fprintf(fp, "SendScriptLine \"set ScriptPlaying 0\"\n");
    fprintf(fp, "puts \"DONE!\"\n");

    G_close_cell(fd);
    fclose(fp);

    sprintf(buf1, _("Created NVIZ script <%s>."), outfile);
    G_done_msg(buf1);

    exit(EXIT_SUCCESS);

}				/* Done with main */



/* ************************************
 * Claculate camera and eye coordinates
**************************************/
int do_profile
    (double e1,
     double e2,
     double n1, double n2, char *name, int fd, int data_type)
{
    float rows, cols, LEN;
    double Y, X, AZI;

    cols = e1 - e2;
    rows = n1 - n2;

    LEN = G_distance(e1, n1, e2, n2);

/* Calculate Azimuth of Line */
    if (rows == 0 && cols == 0) {
/* Special case for no movement */
/* do nothing */
	return 0;
    }

    if (rows >= 0 && cols < 0) {
/* SE Quad or due east */
	AZI = fabs(atan((rows / cols)));
	Y = (double)DIST * sin(AZI);
	X = (double)DIST * cos(AZI);
	if (e != 0.0 && (e != e1 || n != n1)) {
	    dist -= G_distance(e, n, e1, n1);
	}
	read_rast(e2 - X, n2 + Y, LEN, fd, 1, data_type);
	read_rast(e2, n2, LEN, fd, 0, data_type);
    }

    if (rows < 0 && cols <= 0) {
/* NE Quad  or due north */
	AZI = fabs(atan((cols / rows)));
	X = (double)DIST * sin(AZI);
	Y = (double)DIST * cos(AZI);
	if (e != 0.0 && (e != e1 || n != n1)) {
	    dist -= G_distance(e, n, e1, n1);
	}
	read_rast(e2 - X, n2 - Y, LEN, fd, 1, data_type);
	read_rast(e2, n2, LEN, fd, 0, data_type);
    }

    if (rows > 0 && cols >= 0) {
/* SW Quad or due south */
	AZI = fabs(atan((rows / cols)));
	X = (double)DIST * cos(AZI);
	Y = (double)DIST * sin(AZI);
	if (e != 0.0 && (e != e1 || n != n1)) {
	    dist -= G_distance(e, n, e1, n1);
	}
	read_rast(e2 + X, n2 + Y, LEN, fd, 1, data_type);
	read_rast(e2, n2, LEN, fd, 0, data_type);
    }

    if (rows <= 0 && cols > 0) {
/* NW Quad  or due west */
	AZI = fabs(atan((rows / cols)));
	X = (double)DIST * cos(AZI);
	Y = (double)DIST * sin(AZI);
	if (e != 0.0 && (e != e1 || n != n1)) {
	    dist -= G_distance(e, n, e1, n1);
	}
	read_rast(e2 + X, n2 - Y, LEN, fd, 1, data_type);
	read_rast(e2, n2, LEN, fd, 0, data_type);
    }

return 0;
}				/* done with do_profile */


static int move(int x, int y)
{
    D_move_abs(x, y);

    return 0;
}

static int cont(int x, int y)
{
    if (D_cont_abs(x, y)) {	/* clipped */
	change_range = 1;
    }
    else {			/* keep track of left,right x for lines drawn in window */

	if (change_range) {
	    which_range++;
	    min_range[which_range] = max_range[which_range] = x;
	    change_range = 0;
	}
	else {
	    if (x < min_range[which_range])
		min_range[which_range] = x;
	    else if (x > max_range[which_range])
		max_range[which_range] = x;
	}
    }

    return 0;
}

/*****************************
 * read_rast
 * function to get raster value at set location
 * and output nviz script
*****************************/
int read_rast
    (double east,
     double north,
     double dist, int fd, int out_type, RASTER_MAP_TYPE data_type)
{
    int row, col, nrows, ncols;
    struct Cell_head window;
    CELL *cell;
    char buf[1024] = "";
    char buf2[1024];
    FCELL *fcell;
    DCELL *dcell;
    double camera_height;


    G_get_window(&window);
    nrows = window.rows;
    ncols = window.cols;

    row = (int)(0.5+ D_u_to_a_row(north));
    col = (int)(0.5+ D_u_to_a_col(east));

    if (row < 0 || row > nrows || col < 0 || col > ncols) {
	G_debug(3, "Fail: row=%d  nrows=%d   col=%d  ncols=%d",row,nrows,col,ncols);
	G_warning(_("Skipping this point, selected point is outside region. "
	  "Perhaps the camera setback distance puts it beyond the edge?"));
	frame++;
	return 1;
    }


    if (data_type == CELL_TYPE) {
	cell = G_allocate_c_raster_buf();
	if (G_get_c_raster_row(fd, cell, row) < 0)
	    exit(1);

	if (G_is_c_null_value(&cell[col]))
	    camera_height = (double) 9999.;
	else
	    camera_height = (double) cell[col];
    }

    if (data_type == FCELL_TYPE) {
	fcell = G_allocate_f_raster_buf();
	if (G_get_f_raster_row(fd, fcell, row) < 0)
	    exit(1);
	if (G_is_f_null_value(&fcell[col]))
	    camera_height = (double) 9999.;
	else
	    camera_height = (double) fcell[col];
    }

    if (data_type == DCELL_TYPE) {
	dcell = G_allocate_d_raster_buf();
	if (G_get_d_raster_row(fd, dcell, row) < 0)
	    exit(1);
	if (G_is_d_null_value(&dcell[col]))
	    camera_height = (double) 9999.;
	else
	    camera_height = (double) dcell[col];
    }

/* Output script commands */
/*************************/

    /* Set camera Height value */
    if (camera_height == 9999.)
	camera_height = OLD_DEPTH;

    if (height_flag && out_type)
	camera_height = HT;
    else if (!height_flag && out_type)
	camera_height = camera_height + HT;

    if (out_type) {
	/* Set Camera Position */
	sprintf(buf2, "\nSendScriptLine \"Nmove_to_real %f %f %f\"",
		east, north, camera_height);
	key_time += (dist + fabs(camera_height - OLD_DEPTH)) / 10000.;
    }
    else {

	/* Set Center of View */
	sprintf(buf2, "\nSendScriptLine \"Nset_focus %f %f %f\"",
		east - window.west - (window.ew_res / 2),
		north - window.south - (window.ns_res / 2),
		camera_height);

	/* Use frame number for now -- TODO figure even increment
	 * based on no. of frames and distance */
	sprintf(buf,
		"\nSendScriptLine \"Nadd_key %f KF_ALL_MASK 1 0.0\"\n",
		key_time);
	strcat(buf2, buf);
	cnt++;
    }

/* Out to file */
    fprintf(fp, "%s", buf2);
    OLD_DEPTH = camera_height;

    frame++;
    return 0;
}
